Shiro 反序列化漏洞
获取漏洞相关代码
拉取shiro代码
切换到1.0.x分支
查看org.apache.shiro.web.mgt.CookieRememberMeManager继承的org.apache.shiro.mgt.AbstractRememberMeManager类
代码分析
org.apache.shiro.mgt.AbstractRememberMeManager类第80行硬编码了默认的加解密密钥
/**
* The following Base64 string was generated by auto-generating an AES Key:
* <pre>
* AesCipherService aes = new AesCipherService();
* byte[] key = aes.generateNewKey().getEncoded();
* String base64 = Base64.encodeToString(key);
* </pre>
* The value of 'base64' was copied-n-pasted here:
*/
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
搜索“DEFAULT_CIPHER_KEY_BYTES”的调用点,找到一处调用点,在该类的构造方法中。
/**
* Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
* an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
*/
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
setCipherKey方法中,将传入的DEFAULT_CIPHER_KEY_BYTES设置为encryptionCipherKey和decryptionCipherKey。
查看org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals方法。
/**
* Implements the interface method by first {@link #getRememberedSerializedIdentity(SubjectContext) acquiring}
* the remembered serialized byte array. Then it {@link #convertBytesToPrincipals(byte[], SubjectContext) converts}
* them and returns the re-constituted {@link PrincipalCollection}. If no remembered principals could be
* obtained, {@code null} is returned.
* <p/>
* If any exceptions are thrown, the {@link #onRememberedPrincipalFailure(RuntimeException, SubjectContext)} method
* is called to allow any necessary post-processing (such as immediately removing any previously remembered
* values for safety).
*
* @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
* is being used to construct a {@link Subject} instance.
* @return the remembered principals or {@code null} if none could be acquired.
*/
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (bytes != null && bytes.length > 0) {
principals = convertBytesToPrincipals(bytes, subjectContext);
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}
return principals;
}
其中getRememberedSerializedIdentity(subjectContext)方法调用的是org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity,其中值得注意的是,该方法在进行Base64反编码时,先将Base64字符串的长度对4取模,如果字符串长度不是4的倍数,则补齐为4的倍数,补齐字符为等号。
/**
* Sometimes a user agent will send the rememberMe cookie value without padding,
* most likely because {@code =} is a separator in the cookie header.
* <p/>
* Contributed by Luis Arias. Thanks Luis!
*
* @param base64 the base64 encoded String that may need to be padded
* @return the base64 String padded if necessary.
*/
private String ensurePadding(String base64) {
int length = base64.length();
if (length % 4 != 0) {
StringBuilder sb = new StringBuilder(base64);
for (int i = 0; i < length % 4; ++i) {
sb.append('=');
}
base64 = sb.toString();
}
return base64;
}
回到org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals方法,base64反编码后的字符串数组通过org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals方法进行解密和反序列化。